/*
 * Copyright (c) 2022 PATEO CONNECT+ (Nanjing) Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "common_utils.h"
#include "napi/native_api.h"
#include "napi/native_node_api.h"
#include "refbase.h"
#include "voice_assistant_client_manager.h"
#include "voice_assistant_event_target.h"
#include "voice_assistant_log.h"
#include "voice_assistant_napi_tools.h"
#include <assert.h>
#include <thread>

namespace OHOS {
namespace CarVoiceAssistant {

    static napi_ref g_voiceassistantManagerConstructorJS = nullptr;
    static napi_ref g_voiceassistantManagerRef = nullptr;

    napi_value VoiceAssistantManagerConstructor(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("VoiceAssistantManagerConstructor");
        std::size_t argc = 1;
        napi_value argv[1];

        napi_value thisVar = nullptr;
        NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));

        sptr<VoiceAssistantEventTarget> target = new VoiceAssistantEventTarget(env);
        VoiceAssistantClientManager::GetInstance()->SetEventTarget(target);

        return thisVar;
    }

    napi_value GetManager(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("GetManager");

        if (g_voiceassistantManagerRef != nullptr) {
            VOICE_ASSISTANT_LOGI("GetManager isExist");
            napi_value result = nullptr;
            napi_get_reference_value(env, g_voiceassistantManagerRef, &result);
            return result;
        }

        napi_value thisVar = nullptr;
        NAPI_CALL(env,
            napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr));

        napi_value cons = nullptr;
        napi_get_reference_value(env, g_voiceassistantManagerConstructorJS, &cons);

        napi_value result = nullptr;
        napi_new_instance(env, cons, 0, nullptr, &result);

        napi_create_reference(env, result, 1, &g_voiceassistantManagerRef);

        return result;
    }

    napi_value IsEnableWakeUp(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("IsEnableWakeUp");
        bool isEnabled = false;
        VoiceAssistantClientManager::GetInstance()->IsEnableWakeUp(isEnabled);
        return GetBoolToJs(env, isEnabled);
    }

    napi_value EnableWakeUp(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("EnableWakeUp");
        int32_t rst = VoiceAssistantClientManager::GetInstance()->EnableWakeUp();
        return GetIntToJs(env, rst);
    }

    napi_value DisableWakeUp(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("DisableWakeUp");
        int32_t rst = VoiceAssistantClientManager::GetInstance()->DisableWakeUp();
        return GetIntToJs(env, rst);
    }

    napi_value IsRecognizing(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("IsRecognizing");
        bool isRecognizing = false;
        VoiceAssistantClientManager::GetInstance()->IsRecognizing(isRecognizing);
        return GetBoolToJs(env, isRecognizing);
    }

    napi_value StartRecognize(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("StartRecognize");
        CommonUtils::VoiceAssistantErrorCode result = CommonUtils::VOICE_ASSISTANT_ERR;
        VoiceAssistantClientManager::GetInstance()->StartRecognize(result);
        return GetIntToJs(env, result);
    }

    napi_value StopRecognize(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("StopRecognize");
        int32_t rst = VoiceAssistantClientManager::GetInstance()->StopRecognize();
        return GetIntToJs(env, rst);
    }

    napi_value PlayTTS(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("PlayTTS");
        size_t argc = 1;
        napi_value argv[1] = { 0 };
        napi_value thisVar = nullptr;
        void* data = nullptr;

        napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);

        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, argv[0], &valueType);

        if (valueType == napi_string) {
            std::string tts = GetStringProperty(env, argv[0]);
            CommonUtils::VoiceAssistantErrorCode result = CommonUtils::VOICE_ASSISTANT_ERR;
            VoiceAssistantClientManager::GetInstance()->PlayTTS(result, tts);
            return GetIntToJs(env, result);
        } else {
            VOICE_ASSISTANT_LOGI("PlayTTS params is not string");
        }

        return GetIntToJs(env, CommonUtils::VOICE_ASSISTANT_ERR);
    }

    napi_value StopPlayTTS(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("StopPlayTTS");
        int32_t rst = VoiceAssistantClientManager::GetInstance()->StopPlayTTS();
        return GetIntToJs(env, rst);
    }

    napi_value RegisterHotwords(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("RegisterHotwords");
        size_t argc = 1;
        napi_value argv[1] = { 0 };
        napi_value thisVar = nullptr;
        void* data = nullptr;

        napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);

        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, argv[0], &valueType);

        if (valueType == napi_string) {
            std::string hotwords = GetStringProperty(env, argv[0]);
            int32_t rst = VoiceAssistantClientManager::GetInstance()->RegisterHotwords(hotwords);
            return GetIntToJs(env, rst);
        } else {
            VOICE_ASSISTANT_LOGI("RegisterHotwords params is not string");
        }

        return GetIntToJs(env, CommonUtils::VOICE_ASSISTANT_ERR);
    }

    napi_value SetCoord(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("SetCoord-napi");
        size_t argc = 2;
        napi_value argv[2] = { 0 };
        napi_value thisVar = nullptr;
        void* data = nullptr;
        double latitude = 0;
        double longitude = 0;

        napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);

        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, argv[0], &valueType);

        if (valueType == napi_number) {
            latitude = GetDoubleProperty(env, argv[0]);
        } else {
            VOICE_ASSISTANT_LOGI("SetCoord params is not string");
            return GetIntToJs(env, CommonUtils::VOICE_ASSISTANT_ERR);
        }

        napi_typeof(env, argv[1], &valueType);
        if (valueType == napi_number) {
            longitude = GetDoubleProperty(env, argv[1]);
        } else {
            VOICE_ASSISTANT_LOGI("SetCoord params is not string");
            return GetIntToJs(env, CommonUtils::VOICE_ASSISTANT_ERR);
        }
        VOICE_ASSISTANT_LOGI("SetCoord-napi:%{public}f,%{public}f", latitude, longitude);
        int32_t rst = VoiceAssistantClientManager::GetInstance()->SetCoord(latitude, longitude);
        return GetIntToJs(env, rst);
    }

    napi_value ChangeSpeakerType(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("ChangeSpeakerType");
        size_t argc = 1;
        napi_value argv[1] = { 0 };
        napi_value thisVar = nullptr;
        void* data = nullptr;

        napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);

        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, argv[0], &valueType);

        if (valueType == napi_string) {
            std::string speaker = GetStringProperty(env, argv[0]);
            int32_t rst = VoiceAssistantClientManager::GetInstance()->ChangeSpeakerType(speaker);
            return GetIntToJs(env, rst);
        } else {
            VOICE_ASSISTANT_LOGI("ChangeSpeakerType params is not string");
        }

        return GetIntToJs(env, CommonUtils::VOICE_ASSISTANT_ERR);
    }

    napi_value On(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("On");
        size_t argc = 2;
        napi_value argv[2] = { 0 };
        napi_value thisVar = nullptr;
        void* data = nullptr;

        napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);

        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, argv[0], &valueType);
        VoiceAssistantEventType type = static_cast<VoiceAssistantEventType>(GetIntProperty(env, argv[0]));

        VoiceAssistantClientManager::GetInstance()->GetEventTarget()->On(env, type, argv[1], thisVar);

        return GetUndefinedToJS(env);
    }

    napi_value Off(napi_env env, napi_callback_info info)
    {
        VOICE_ASSISTANT_LOGI("Off");
        size_t argc = 1;
        napi_value argv[1] = { 0 }; /* 参数定义 */
        napi_value thisVar = nullptr; /* JS对象this */
        void* data = nullptr;

        napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);

        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, argv[0], &valueType);
        VoiceAssistantEventType type = static_cast<VoiceAssistantEventType>(GetIntProperty(env, argv[0]));

        VoiceAssistantClientManager::GetInstance()->GetEventTarget()->Off(env, type, thisVar);

        return GetUndefinedToJS(env);
    }

    void CreateErrorCodeEnum(napi_env env, napi_value object)
    {
        VOICE_ASSISTANT_LOGI("CreateErrorCodeEnum");
        napi_value okJs = nullptr;
        napi_create_int32(env, CommonUtils::VOICE_ASSISTANT_OK, &okJs);
        napi_set_named_property(env, object, "VOICE_ASSISTANT_OK", okJs);

        napi_value errJs = nullptr;
        napi_create_int32(env, CommonUtils::VOICE_ASSISTANT_ERR, &errJs);
        napi_set_named_property(env, object, "VOICE_ASSISTANT_ERR", errJs);

        napi_value recordFailedJs = nullptr;
        napi_create_int32(env, CommonUtils::VOICE_ASSISTANT_START_RECORD_FAILED, &recordFailedJs);
        napi_set_named_property(env, object, "RADIO_STATUS_SCANNING", recordFailedJs);

        napi_value websocketFailedJs = nullptr;
        napi_create_int32(env, CommonUtils::VOICE_ASSISTANT_START_WEBSOCKET_CONNECT_FAILED, &websocketFailedJs);
        napi_set_named_property(env, object, "VOICE_ASSISTANT_START_WEBSOCKET_CONNECT_FAILED", websocketFailedJs);
    }

    void CreateEventTypeEnum(napi_env env, napi_value object)
    {
        VOICE_ASSISTANT_LOGI("CreateEventTypeEnum");
        napi_value wakeUpJs = nullptr;
        napi_create_int32(env, VoiceAssistantEventTypeOnWakeUp, &wakeUpJs);
        napi_set_named_property(env, object, "VoiceAssistantEventTypeOnWakeUp", wakeUpJs);

        napi_value recognizeStateChangedJs = nullptr;
        napi_create_int32(env, VoiceAssistantEventTypeRecognizeStateChanged, &recognizeStateChangedJs);
        napi_set_named_property(env, object, "VoiceAssistantEventTypeRecognizeStateChanged", recognizeStateChangedJs);

        napi_value asrResultJs = nullptr;
        napi_create_int32(env, VoiceAssistantEventTypeAsrResult, &asrResultJs);
        napi_set_named_property(env, object, "VoiceAssistantEventTypeAsrResult", asrResultJs);

        napi_value ttsStateChangedJs = nullptr;
        napi_create_int32(env, VoiceAssistantEventTypeTTSPlayStateChanged, &ttsStateChangedJs);
        napi_set_named_property(env, object, "VoiceAssistantEventTypeTTSPlayStateChanged", ttsStateChangedJs);
    }

    static napi_value GetManagerFuction(napi_env env, napi_value exports)
    {

        VOICE_ASSISTANT_LOGI("GetManagerFuction");
        // ErrorCode enum
        napi_value errorCodeJs = nullptr;
        napi_create_object(env, &errorCodeJs);
        CreateErrorCodeEnum(env, errorCodeJs);

        // EventType enum
        napi_value eventTypeJs = nullptr;
        napi_create_object(env, &eventTypeJs);
        CreateEventTypeEnum(env, eventTypeJs);

        napi_status status;
        napi_property_descriptor desc[] = {
            DECLARE_NAPI_FUNCTION("getManager", GetManager),
            DECLARE_NAPI_PROPERTY("ErrorCode", errorCodeJs),
            DECLARE_NAPI_PROPERTY("EventType", eventTypeJs)
        };
        status = napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]),
            desc);
        assert(status == napi_ok);

        return (exports);
    }

    static napi_value RegisterManagerFuction(napi_env env, napi_value exports)
    {
        VOICE_ASSISTANT_LOGI("RegisterManagerFuction");

        napi_status status;
        napi_property_descriptor desc[] = {
            DECLARE_NAPI_FUNCTION("isEnableWakeUp", IsEnableWakeUp),
            DECLARE_NAPI_FUNCTION("enableWakeUp", EnableWakeUp),
            DECLARE_NAPI_FUNCTION("disableWakeUp", DisableWakeUp),
            DECLARE_NAPI_FUNCTION("isRecognizing", IsRecognizing),
            DECLARE_NAPI_FUNCTION("startRecognize", StartRecognize),
            DECLARE_NAPI_FUNCTION("stopRecognize", StopRecognize),
            DECLARE_NAPI_FUNCTION("playTTS", PlayTTS),
            DECLARE_NAPI_FUNCTION("stopPlayTTS", StopPlayTTS),
            DECLARE_NAPI_FUNCTION("registerHotwords", RegisterHotwords),
            DECLARE_NAPI_FUNCTION("setCoord", SetCoord),
            DECLARE_NAPI_FUNCTION("changeSpeakerType", ChangeSpeakerType),
            DECLARE_NAPI_FUNCTION("on", On),
            DECLARE_NAPI_FUNCTION("off", Off)
        };

        napi_value cons = nullptr;
        status = napi_define_class(env, "CarVoiceAssistantManager", NAPI_AUTO_LENGTH,
            VoiceAssistantManagerConstructor, nullptr,
            sizeof(desc) / sizeof(desc[0]), desc, &cons);
        assert(status == napi_ok);

        napi_create_reference(env, cons, 1, &g_voiceassistantManagerConstructorJS);

        return (exports);
    }

    static napi_value Init(napi_env env, napi_value exports)
    {
        RegisterManagerFuction(env, exports);
        GetManagerFuction(env, exports);

        return (exports);
    }

    /* NAPI_MODULE(js_radio, Init) */

    static napi_module sampleModule = {
        .nm_version = 1,
        .nm_flags = 0,
        .nm_filename = nullptr,
        .nm_register_func = Init,
        .nm_modname = "carvoiceassistant",
        .nm_priv = ((void*)0),
        .reserved = { 0 },
    };

    extern "C" __attribute__((constructor)) void Register()
    {
        napi_module_register(&sampleModule);
    }
} // namespace TUNER
} // namespace OHOS